package com.mdb.open.epay.facade.epay;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.mdb.bc.api.data.ListResultEx;
import com.mdb.bc.api.data.ResultEx;
import com.mdb.bc.utils.BeanConvertUtil;
import com.mdb.bc.utils.DateTimeUtil;
import com.mdb.bc.utils.ObjectUtil;
import com.mdb.open.common.enums.DataChannelEnum;
import com.mdb.open.common.enums.ProductCodeEnum;
import com.mdb.open.epay.cache.PayOtpBean;
import com.mdb.open.epay.config.CoreConfig;
import com.mdb.open.epay.config.EPayOpenProperties;
import com.mdb.open.epay.domain.bo.*;
import com.mdb.open.epay.domain.ko.CustDayOrderStatKo;
import com.mdb.open.epay.domain.ko.StatCustDayOrderKo;
import com.mdb.open.epay.domain.ko.StatUserDayOrderKo;
import com.mdb.open.epay.domain.ko.UserDayOrderStatKo;
import com.mdb.open.epay.domain.po.epay.*;
import com.mdb.open.epay.domain.po.product.Product;
import com.mdb.open.epay.domain.vo.epay.*;
import com.mdb.open.epay.enums.constant.CoreCancelFlgEnum;
import com.mdb.open.epay.enums.constant.CoreInqFlgEnum;
import com.mdb.open.epay.enums.constant.CoreTransStatusEnum;
import com.mdb.open.epay.enums.constant.SMSEnum;
import com.mdb.open.epay.enums.retcode.CoreRetCodeEnum;
import com.mdb.open.epay.enums.status.*;
import com.mdb.open.epay.enums.type.*;
import com.mdb.open.epay.rabbit.producer.RetryQpOrderProducer;
import com.mdb.open.epay.rabbit.producer.RetryQpRefundOrderProducer;
import com.mdb.open.epay.service.customer.CustBaseInfoService;
import com.mdb.open.epay.service.epay.*;
import com.mdb.open.epay.service.epay.biz.bo.*;
import com.mdb.open.epay.service.epay.biz.service.TradeBizService;
import com.mdb.open.epay.service.epay.handle.CoreSMSHandler;
import com.mdb.open.epay.service.epay.handle.CoreTradeHandle;
import com.mdb.open.epay.service.epay.handle.QueryCoreHandle;
import com.mdb.open.epay.service.epay.handle.dto.*;
import com.mdb.open.epay.service.epay.handle.vo.CoreCancelVo;
import com.mdb.open.epay.service.epay.handle.vo.CoreCustVo;
import com.mdb.open.epay.service.epay.handle.vo.CoreTradeVo;
import com.mdb.open.epay.service.epay.handle.vo.CoreTranVo;
import com.mdb.open.epay.service.product.ProductService;
import com.mdb.open.epay.utils.FeeCalculateUtil;
import com.mdb.open.epay.utils.NoGenUtil;
import com.mdb.open.epay.validator.OtpValidator;
import com.mdb.open.epay.validator.TradeValidator;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 支付 订单
 *
 * @author liuyi
 * @date 2019/11/14
 **/
@Log4j2
@Service
public class TradeFacade {


    @Autowired
    private QpSignService qpSignService;

    @Autowired
    private QpOrderService qpOrderService;

    @Autowired
    private QpRefundOrderService qpRefundOrderService;

    @Autowired
    private EpQuotaLimitService epQuotaLimitService;

    @Autowired
    private TradeValidator tradeValidator;

    @Autowired
    private EpFeeConfigService epFeeConfigService;

    @Autowired
    private OtpValidator otpValidator;

    @Autowired
    private EPayOpenProperties ePayOpenProperties;

    @Autowired
    private QueryCoreHandle queryCoreHandle;

    @Autowired
    private CoreTradeHandle coreTradeHandle;

    @Autowired
    private CustBaseInfoService custBaseInfoService;

    @Autowired
    private ProductService productService;

    @Autowired
    private TradeBizService tradeBizService;

    @Autowired
    private CoreSMSHandler coreSMSHandler;

    @Autowired
    private RetryQpOrderProducer retryQpOrderProducer;

    @Autowired
    private RetryQpRefundOrderProducer retryQpRefundOrderProducer;

    @Autowired
    private EpQuickpayConfigService epQuickpayConfigService;

    /**
     * 發送支付驗證短信
     */
    public ResultEx sendPayOtp(SendPayOtpBo bo) {
        //1驗證簽約狀態
        QpSign existQpSign = qpSignService.getBySignNo(bo.getCustomerCode(), bo.getBankSignNo());
        if (ObjectUtil.isEmpty(existQpSign)) {
            return ResultEx.makeDataNotExistsResult("签约信息不存在");//TODO 签约信息不存在
        }
        if (!QpSignStatusEnum.SIGNED.getValue().equals(existQpSign.getStatus())) {
            return ResultEx.fail("簽約狀態異常！");//TODO 不是开通状态
        }

        if(qpOrderService.existed(bo.getCustomerCode(), bo.getOrderNo())) {
            return ResultEx.fail("訂單號不可重複");
        }
        //校验手机号是否匹配
        GetCoreCustDto getCoreCustDto = new GetCoreCustDto();
        getCoreCustDto.setCiNo(existQpSign.getCoreClientNo());
        getCoreCustDto.setType(CoreConfig.CUST_TYPE_H);
        log.info("調用核心，查詢客戶信息");
        ResultEx<CoreCustVo> resultEx = queryCoreHandle.getCoreCust(getCoreCustDto);
        if (resultEx.isFailed()) {
            return ResultEx.fail("客戶信息查詢失敗");//TODO 客戶信息查詢失敗
        }
        CoreCustVo coreCustVo = resultEx.getData();
        //查询核心接口，获取客户号，根据客户号查询签约表
        if (ePayOpenProperties.isPhoneCheck()) {
            //  4.1证件类型，证件号查询客户号及预留手机号，手机少是否一致 TODO 手机号区域 +86  86
            if (!bo.getPhoneArea().equals(coreCustVo.getEaddrMobCntyno())
                    || !bo.getPhoneNo().equals(coreCustVo.getEaddrMobNo())) {
                return ResultEx.fail("預留手機號不一致");//TODO 預留手機號不一致
            }
        } else {
            log.error("未檢校驗留手機號是否一致，請配置");
        }

        //2驗證限額限次
        //用户单日限额
        EpQuotaLimit userQpQuotaLimit = epQuotaLimitService.getUserQpQuotaLimit(existQpSign.getCoreClientNo(), existQpSign.getBankAccNo(), QuotaLimitTypeEnum.QUOTA, CcyTypeEnum.MOP);
        //用户单日限次
        EpQuotaLimit userQpTimesLimit = epQuotaLimitService.getUserQpQuotaLimit(existQpSign.getCoreClientNo(), existQpSign.getBankAccNo(), QuotaLimitTypeEnum.TIMES, null);
        //客户单笔限额
        EpQuotaLimit customerQpSingleQuotaLimit = epQuotaLimitService.getCustomerSigleQuotaLimit(bo.getCustomerCode());

        if (ObjectUtil.isAnyEmpty(userQpQuotaLimit, userQpTimesLimit, customerQpSingleQuotaLimit)
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(userQpQuotaLimit.getStatus())
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(userQpTimesLimit.getStatus())
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(customerQpSingleQuotaLimit.getStatus())) {
            return ResultEx.fail("限額限次配置缺失或狀態異常");//TODO 限額限次配置缺失或狀態異常
        }

        //交易日限额，限次验证  自然日
        //查詢當日交易成功次數
        //查詢當日交易總額
        StatUserDayOrderAmtBo statUserDayOrderAmtBo = new StatUserDayOrderAmtBo();
        statUserDayOrderAmtBo.setBankAccNo(existQpSign.getBankAccNo());
        statUserDayOrderAmtBo.setDate(LocalDate.now());
        statUserDayOrderAmtBo.setStatusList(Lists.newArrayList(QpOrderStatusEnum.TIMEOUT, QpOrderStatusEnum.SUCCESS));
        ResultEx<UserDayOrderStatVo> userDayOrderStatVoResultEx = this.statUserDayOrderAmt(statUserDayOrderAmtBo);
        UserDayOrderStatVo userDayOrderStatKo = userDayOrderStatVoResultEx.getData();
        //今日用户实际支出
        BigDecimal userDayRealPayAmt = userDayOrderStatKo.getOrderSumAmt()
                .subtract(userDayOrderStatKo.getRefundSumAmt())
                .subtract(userDayOrderStatKo.getRefundingSumAmt());
        //今日用户实际交易次数
        Integer userDayRealPayCount = userDayOrderStatKo.getOrderCount();

        if (userDayRealPayAmt.compareTo(new BigDecimal(userQpQuotaLimit.getDayLimit())) > 0) {
            return ResultEx.fail("今日交易額已達上限");//TODO 今日交易額已達上限
        }
        if (userDayRealPayCount >= Integer.parseInt(userQpTimesLimit.getDayLimit())) {
            return ResultEx.fail("今日交易次數已達上限");//TODO 今日交易次數已達上限
        }

        //3發送OTP 並與訂單號關聯
        String otpCode = NoGenUtil.genRandomOtpCode();
        LocalDateTime now = LocalDateTime.now();
        if (ePayOpenProperties.isOtpCheck()) {
            log.info("支付调用核心短信发送！");
            SendCoreSMSDto sendCoreSMSDto = new SendCoreSMSDto();
            sendCoreSMSDto.setSmsTemplateId(CoreConfig.SMS_TEMPLATE_PAYOPT);
            sendCoreSMSDto.setTransactionType(SMSEnum.TRANS_TYPE_TEMPLATE.getValue());
            sendCoreSMSDto.setTransactionName(CoreConfig.TRANS_NAME_PAY);
            sendCoreSMSDto.setSendType(SMSEnum.SEND_TYPE_SMS.getValue());
            sendCoreSMSDto.setAccountNo(existQpSign.getBankAccNo());
            sendCoreSMSDto.setOpt(otpCode);
            sendCoreSMSDto.setPhoneNo(bo.getPhoneArea() + bo.getPhoneNo());
            sendCoreSMSDto.setDate(DateTimeUtil.convert2Str(now, ePayOpenProperties.getSmsDatePatter()));
            sendCoreSMSDto.setTime(DateTimeUtil.convert2Str(now, ePayOpenProperties.getSmsTimePatter()));
            sendCoreSMSDto.setMessageLength("480");
            sendCoreSMSDto.setAction("簽約快捷支付");
            //sendCoreSMSDto.setAmount(" ");
            //sendCoreSMSDto.setCurrency(" ");
            sendCoreSMSDto.setFiller(" ");
            sendCoreSMSDto.setSendDate("00000000");
            sendCoreSMSDto.setSendTime("000000");

            ResultEx smsResultEx = coreSMSHandler.sendCoreSMS(sendCoreSMSDto);
            if (smsResultEx.isFailed()) {
                return ResultEx.fail("發送短信驗證碼失敗");//TODO 發送短信驗證碼失敗
            }
        } else {
            log.error("未開啟校驗短信驗證碼，請配置");
        }
        otpValidator.setTradeOtpCache(bo, otpCode);
        log.info("短信内容存入缓存：{}", otpCode);

        return ResultEx.ok();
    }

    /**
     * 快捷支付
     */
    public ResultEx<PayVo> qpPay(PayBo bo) {
        if (!CustTypeEnum.P.getValue().equals(bo.getCustType().getValue())) {
            return ResultEx.fail("暫不支持非個人客戶！");//TODO 暫不支持非個人客戶
        }
        Product productInfo = productService.getByCode(ProductCodeEnum.EP_QP.getValue());
        if (ObjectUtil.isEmpty(productInfo)) {
            return ResultEx.fail();//TODO 系統異常，未知錯誤（產品信息未配置）
        }

        //檢查唯一索引問題。
        if(qpOrderService.existed(bo.getCustomerCode(), bo.getOrderNo())) {
            return ResultEx.fail("訂單號不可重複");
        }

        //是否需要OTP驗證，如果需要，驗證msgCode字段是否為空
        //用户OTP限额
        EpQuotaLimit userQpOtpLimit = epQuotaLimitService.getUserQpOtpLimit(bo.getCustomerCode(), CcyTypeEnum.MOP);
        if (ObjectUtil.isEmpty(userQpOtpLimit)) {
            return ResultEx.fail("客戶OTP限額未配置");//TODO 系统异常，客户OTP限额未配置
        }
        BigDecimal optQuotaLimit = new BigDecimal(userQpOtpLimit.getSingleLimit());
        if (optQuotaLimit.compareTo(bo.getOrderAmt()) < 0) {
            if (ObjectUtil.isEmpty(bo.getMsgCode())) {
                return ResultEx.fail("需要先发送短信验证码");//TODO 需要先发送短信验证码
            }
            //短信驗證碼驗證
            ResultEx<PayOtpBean> payOtpBeanResultEx = otpValidator.checkTradeOtp(bo);
            if (payOtpBeanResultEx.isFailed()) {
                return ResultEx.makeResult(payOtpBeanResultEx.getRetCode(), payOtpBeanResultEx.getRetMsg());
            }
        }

        //1 驗證簽約狀態
        QpSign existQpSign = qpSignService.getBySignNo(bo.getCustomerCode(), bo.getBankSignNo());
        if (ObjectUtil.isEmpty(existQpSign)) {
            return ResultEx.makeDataNotExistsResult("签约信息不存在");//TODO 签约信息不存在
        }
        if (!QpSignStatusEnum.SIGNED.getValue().equals(existQpSign.getStatus())) {
            return ResultEx.fail("簽約狀態異常！");//TODO 不是开通状态，返回成功还是失败
        }

        //2 限額限次驗證  自然日
        //获取限额限次信息
        //用户单日限额
        EpQuotaLimit userQpQuotaLimit = epQuotaLimitService.getUserQpQuotaLimit(existQpSign.getCoreClientNo(), existQpSign.getBankAccNo(), QuotaLimitTypeEnum.QUOTA, CcyTypeEnum.MOP);
        //用户单日限次
        EpQuotaLimit userQpTimesLimit = epQuotaLimitService.getUserQpQuotaLimit(existQpSign.getCoreClientNo(), existQpSign.getBankAccNo(), QuotaLimitTypeEnum.TIMES, null);
        //客户单笔限额
        EpQuotaLimit customerQpSingleQuotaLimit = epQuotaLimitService.getCustomerSigleQuotaLimit(bo.getCustomerCode());

        if (ObjectUtil.isAnyEmpty(userQpQuotaLimit, userQpTimesLimit, customerQpSingleQuotaLimit)
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(userQpQuotaLimit.getStatus())
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(userQpTimesLimit.getStatus())
                || !QuotaLimitStatusEnum.NORMAL.getValue().equals(customerQpSingleQuotaLimit.getStatus())) {
            return ResultEx.fail("限額限次配置缺失或狀態異常");//TODO 限額限次配置缺失或狀態異常
        }

        //单笔限额验证
        if (bo.getOrderAmt().compareTo(new BigDecimal(customerQpSingleQuotaLimit.getSingleLimit())) > 0) {
            return ResultEx.fail("超出單筆限額");//TODO 超出单笔限额
        }
        //交易日限额，限次验证
        //查詢當日交易總額
        StatUserDayOrderAmtBo statUserDayOrderAmtBo = new StatUserDayOrderAmtBo();
        statUserDayOrderAmtBo.setBankAccNo(existQpSign.getBankAccNo());
        statUserDayOrderAmtBo.setDate(LocalDate.now());
        statUserDayOrderAmtBo.setStatusList(Lists.newArrayList(QpOrderStatusEnum.TIMEOUT, QpOrderStatusEnum.SUCCESS));
        ResultEx<UserDayOrderStatVo> userDayOrderStatVoResultEx = this.statUserDayOrderAmt(statUserDayOrderAmtBo);
        UserDayOrderStatVo userDayOrderStatKo = userDayOrderStatVoResultEx.getData();
        //今日用户实际支出
        BigDecimal userDayRealPayAmt = userDayOrderStatKo.getOrderSumAmt()
                .subtract(userDayOrderStatKo.getRefundSumAmt())
                .subtract(userDayOrderStatKo.getRefundingSumAmt());
        //今日用户实际交易次数
        Integer userDayRealPayCount = userDayOrderStatKo.getOrderCount();

        if (userDayRealPayAmt.add(bo.getOrderAmt()).compareTo(new BigDecimal(userQpQuotaLimit.getDayLimit())) > 0) {
            return ResultEx.fail("今日交易額已達上限");//TODO 今日交易額已達上限
        }
        if (userDayRealPayCount >= Integer.parseInt(userQpTimesLimit.getDayLimit())) {
            return ResultEx.fail("今日交易次數已達上限");//TODO 今日交易次數已達上限
        }

        EpQuickpayConfig epQuickpayConfig = epQuickpayConfigService.get(ProductCodeEnum.EP_QP.getValue());
        if (ObjectUtil.isEmpty(epQuickpayConfig) || ObjectUtil.isEmpty(epQuickpayConfig.getSuspendAccNo())) {
            return ResultEx.fail("未配置產品信息");//TODO 未配置產品信息
        }

        //4 計算手續費
        EpFeeConfig customerQpFeeConfig = epFeeConfigService.getCustomerQpFeeConfig(bo.getCustomerCode());
        if (ObjectUtil.isEmpty(customerQpFeeConfig)) {
            return ResultEx.fail("客戶手續費未配置");//TODO 客戶手續費未配置
        }
        BigDecimal feeValue = FeeCalculateUtil.calculateFee(customerQpFeeConfig, bo.getOrderAmt());

        //5 生成訂單
        String innerOrderNo = NoGenUtil.genOrderNo();
        QpOrder qpOrder = new QpOrder();
        qpOrder.setCustomerCode(existQpSign.getCustomerCode());
        qpOrder.setCustomerName(existQpSign.getCustomerName());
        qpOrder.setProdCode(productInfo.getProdCode());
        qpOrder.setProdName(productInfo.getProdName());
        qpOrder.setInnerOrderNo(innerOrderNo);
        qpOrder.setCustOrderNo(bo.getOrderNo());
        qpOrder.setAmount(bo.getOrderAmt());
        qpOrder.setRefundingAmt(BigDecimal.ZERO);
        qpOrder.setRefundAmt(BigDecimal.ZERO);
        qpOrder.setRefundFeeAmt(BigDecimal.ZERO);
        qpOrder.setRefundingFeeAmt(BigDecimal.ZERO);
        qpOrder.setGoodsDesc(bo.getRemark());
        qpOrder.setStatus(QpOrderStatusEnum.PROCESSING.getValue());
        qpOrder.setSignNo(bo.getBankSignNo());
        qpOrder.setCardType(existQpSign.getCardType());
        qpOrder.setBankAccNo(existQpSign.getBankAccNo());
        qpOrder.setBankAccName(existQpSign.getBankAccEname());
        qpOrder.setCertNo(existQpSign.getCertNo());
        qpOrder.setCertType(existQpSign.getCertType());
        qpOrder.setPhoneArea(existQpSign.getPhoneArea());
        qpOrder.setPhoneNo(existQpSign.getPhoneNo());
        qpOrder.setCcy(bo.getCurrency().getValue());
        qpOrder.setTradeType(bo.getTradeType().getValue());
        qpOrder.setCustOrderTime(LocalDateTime.parse(bo.getOrderTime(), DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN)));
//        qpOrder.setNotifyUrl(bo.getNotifyUrl());
//        qpOrder.setNotifyStatus(bo.getNotifyStatus());
        qpOrder.setRemark(bo.getRemark());
        qpOrder.setReqTime(LocalDateTime.now());
        qpOrder.setReqSourceType(DataChannelEnum.API.getValue());
        qpOrder.setFee(feeValue);
        qpOrder.setFeeConfigType(customerQpFeeConfig.getFeeType());
        qpOrder.setFeeConfigValue(customerQpFeeConfig.getFeeValue());
        qpOrder.setReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());
        qpOrder.setFeeReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());

        //保存訂單信息
        boolean saveQpOrder = qpOrderService.save(qpOrder);
        if (!saveQpOrder) {
            return ResultEx.fail("系統異常，未知錯誤！");//TODO 系統異常，未知錯誤！
        }

        //6 發起核心交易
        String sameCcy = qpOrder.getCcy();
        CoreTradeDto coreTradeDto = new CoreTradeDto();
        coreTradeDto.setCcy(sameCcy);
        coreTradeDto.setDrCcy(sameCcy);
        coreTradeDto.setCrCcy(sameCcy);
        coreTradeDto.setAmt(qpOrder.getAmount().toPlainString());
        //借方
        coreTradeDto.setDacTyp(AccountEnum.D);
        coreTradeDto.setDrAc(qpOrder.getBankAccNo());
        //贷方
        coreTradeDto.setCacTyp(AccountEnum.I);
        coreTradeDto.setCrAc(epQuickpayConfig.getSuspendAccNo());
        ResultEx<CoreTradeVo> coreTradeVoResultEx = coreTradeHandle.coreTrade(coreTradeDto, qpOrder.getInnerOrderNo(), ePayOpenProperties.getCoreTranRemark());
        PayVo payVo = new PayVo();
        payVo.setBankSignNo(existQpSign.getSignNo());
        payVo.setBankSerialNo(innerOrderNo);
        payVo.setOrderNo(bo.getOrderNo());
        payVo.setPayAmt(qpOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
        payVo.setPayCurrency(qpOrder.getCcy());

        //7 根據核心結果，修改訂單狀態並返回
        //核心失敗 核心交易失败原因
        //核心成功
        CoreTradeVo coreTradeVo = coreTradeVoResultEx.getData();
        if (coreTradeVoResultEx.isFailed()
                || ObjectUtil.isEmpty(coreTradeVo)
                || ObjectUtil.isEmpty(coreTradeVo.getCMG26())) {
            log.info("調用核心失敗！");
            //结果未知 处理中 TODO 作調用超時處理 訂單狀態未知  定時任務，隊列同時處理
            if(CoreRetCodeEnum.DD1452.getValue().equals(coreTradeVoResultEx.getRetCode())) {
                TradeFailBizBo tradeFailBizBo = new TradeFailBizBo();
                tradeFailBizBo.setInnerOrderNo(qpOrder.getInnerOrderNo());
                tradeFailBizBo.setOldStatus(qpOrder.getStatus());
                tradeFailBizBo.setNewStatus(QpRefundOrderStatusEnum.FAIL.getValue());
                tradeFailBizBo.setCoreOrderNo(coreTradeVoResultEx.getRetCode());
                ResultEx failBizResultEx = tradeBizService.tradeFail(tradeFailBizBo);
                if (failBizResultEx.isFailed()) {
                    log.info("核心支付失敗");
                }
                return ResultEx.makeResult(CoreRetCodeEnum.DD1452, payVo);
            }
            TradeTimeoutBizBo tradeTimeoutBizBo = new TradeTimeoutBizBo();
            tradeTimeoutBizBo.setInnerOrderNo(qpOrder.getInnerOrderNo());
            tradeTimeoutBizBo.setOldStatus(qpOrder.getStatus());
            tradeTimeoutBizBo.setNewStatus(QpRefundOrderStatusEnum.TIMEOUT.getValue());
            tradeTimeoutBizBo.setErrorCode(coreTradeVoResultEx.getRetCode());
            //TODO tradeTimeoutBizBo.setErrorMsg();
            //TODO 交易時間
            ResultEx failBizResultEx = tradeBizService.tradeTimeout(tradeTimeoutBizBo);
            if (failBizResultEx.isFailed()) {
                //TODO 订单状态修改超時处理
            }
            return ResultEx.makeResult(CoreRetCodeEnum.CORE_FAIL, payVo);
        } else {
            LocalDateTime transDate = coreTradeVo.getTradeDate();
            String coreOrderNo = coreTradeVo.getCMG26().getJrnNo();

            TradeSuccessBizBo successBizBo = new TradeSuccessBizBo();
            successBizBo.setInnerOrderNo(qpOrder.getInnerOrderNo());
            successBizBo.setOldStatus(qpOrder.getStatus());
            successBizBo.setNewStatus(QpOrderStatusEnum.SUCCESS.getValue());
            successBizBo.setCoreOrderNo(coreOrderNo);
            successBizBo.setTransDate(coreTradeVo.getTradeDate());
            ResultEx successBizResultEx = tradeBizService.tradeSuccess(successBizBo);
            if (successBizResultEx.isFailed()) {
                //TODO 订单状态修改失败处理
                log.error("订单状态修改失败处理");
            }
            payVo.setTfrDate(ObjectUtil.isNotEmpty(transDate) ? transDate.format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN)) : null);
            return ResultEx.ok(payVo);
        }
    }

    /**
     * 快捷退款
     */
    public ResultEx<RefundVo> qpRefund(RefundBo bo) {
        if (!CustTypeEnum.P.getValue().equals(bo.getCustType().getValue())) {
            return ResultEx.fail("暫不支持非個人客戶！");//TODO 暫不支持非個人客戶
        }
        //查詢原始訂單
        QpOrder oriOrder = qpOrderService.getByCustOrderNo(bo.getCustomerCode(), bo.getOriOrderNo());
        if (ObjectUtil.isEmpty(oriOrder)) {
            return ResultEx.fail("原始訂單不存在");//TODO
        }
        if (!QpOrderStatusEnum.SUCCESS.getValue().equals(oriOrder.getStatus())) {
            return ResultEx.fail("訂單狀態異常，不允許退款");//TODO
        }
        if (!StrUtil.equals(oriOrder.getCustomerCode(), bo.getCustomerCode())) {
            return ResultEx.fail("不是自己的訂單");//TODO
        }

        if (RefundBizTypeEnum.CANCEL.equals(bo.getBizType())) {
            return this.qpCancel(bo);
        }

        if (oriOrder.getCreateTime().plusDays(ePayOpenProperties.getRefundMaxValidDays()).isBefore(LocalDate.now().atStartOfDay())) {
            return ResultEx.fail("訂單創建日期超過可退日期範圍"); //TODO
        }

        //檢查唯一索引問題。
        if(qpRefundOrderService.existed(bo.getCustomerCode(), bo.getReOrderNo())) {
            return ResultEx.fail("訂單號不可重複");
        }


//        //是否是當日交易
//        if (oriOrder.getCreateTime().isAfter(LocalDate.now().atStartOfDay())) {
//            //當日交易，走沖正
//            return ResultEx.fail("當日訂單無法退款，可撤銷"); //TODO
//        }

        BigDecimal all = oriOrder.getRefundAmt().add(oriOrder.getRefundingAmt()).add(bo.getBackReAmt());
        if (all.compareTo(oriOrder.getAmount()) > 0) {
            return ResultEx.fail("退款金額大於原訂單可退金額！");//TODO
        }
        //計算退款手續費
        BigDecimal reFundFeeAmt = FeeCalculateUtil.calculateFee(oriOrder.getFeeConfigType(), oriOrder.getFeeConfigValue(), bo.getBackReAmt());
        if (oriOrder.getRefundFeeAmt().add(oriOrder.getRefundingFeeAmt()).add(reFundFeeAmt).compareTo(oriOrder.getFee()) > 0) {
            //退款手續費高於全部手續費時
            reFundFeeAmt = oriOrder.getFee()
                    .subtract(oriOrder.getRefundFeeAmt())
                    .subtract(oriOrder.getRefundingFeeAmt())
                    .setScale(2, RoundingMode.HALF_UP);
        }
        //最后一次退款手续费计算
        //加上本次退款總金額是否達到原訂單金額，如果達到 則手續費退剩餘全部
        if (bo.getBackReAmt().add(oriOrder.getRefundingAmt()).add(oriOrder.getRefundAmt()).compareTo(oriOrder.getAmount()) == 0) {
            reFundFeeAmt = oriOrder.getFee()
                    .subtract(oriOrder.getRefundFeeAmt())
                    .subtract(oriOrder.getRefundingFeeAmt())
                    .setScale(2, RoundingMode.HALF_UP);
        }

        BigDecimal customerRefundAmt = bo.getBackReAmt().subtract(reFundFeeAmt);

        //計算當前交易日 所有成功交易是否滿足退款條件 自然日
        //統計所有當前交易日所有交易總額及手續費總額
        //客戶當前交易餘額>customerRefundAmt,客戶當前交易日收取
        //查詢當日交易總額
        StatCustDayOrderAmtBo statCustDayOrder = new StatCustDayOrderAmtBo();
        statCustDayOrder.setCustomerCode(oriOrder.getCustomerCode());
        statCustDayOrder.setDate(LocalDate.now());
        statCustDayOrder.setStatusList(Lists.newArrayList(QpOrderStatusEnum.SUCCESS));
        ResultEx<CustDayOrderStatVo> custDayOrderStatVoResultEx = this.statCustDayOrderAmt(statCustDayOrder);
        CustDayOrderStatVo custDayOrderStatKo = custDayOrderStatVoResultEx.getData();
        //今日客户实际支出
        BigDecimal custDayRealPayAmt = custDayOrderStatKo.getOrderSumAmt()
                .subtract(custDayOrderStatKo.getRefundSumAmt())
                .subtract(custDayOrderStatKo.getRefundingSumAmt());
        //实际收取手续费
        BigDecimal custDayRealFeeAmt = custDayOrderStatKo.getFeeSumAmt()
                .subtract(custDayOrderStatKo.getRefundFeeSumAmt())
                .subtract(custDayOrderStatKo.getRefundingFeeSumAmt());

        //判斷今日客戶入賬 及 銀行手續費入賬金額是否滿足退款要求
        if (customerRefundAmt.compareTo(custDayRealPayAmt) > 0) {
            return ResultEx.fail("當日客戶入賬少於退款金額");//TODO 當日客戶入賬少於退款金額
        }
        if (reFundFeeAmt.compareTo(custDayRealFeeAmt) > 0) {
            return ResultEx.fail("當日客戶手續費入賬少於退款手續費金額");//TODO 當日客戶手續費入賬少於退款手續費金額
        }

        EpQuickpayConfig epQuickpayConfig = epQuickpayConfigService.get(ProductCodeEnum.EP_QP.getValue());
        if (ObjectUtil.isEmpty(epQuickpayConfig) || ObjectUtil.isEmpty(epQuickpayConfig.getSuspendAccNo())) {
            return ResultEx.fail("未配置產品信息");//TODO 未配置產品信息
        }

        LocalDateTime now = LocalDateTime.now();
        //創建退款訂單
        String refundOrderNo = NoGenUtil.genRefundOrderNo();
        QpRefundOrder qpRefundOrder = new QpRefundOrder();
        qpRefundOrder.setProdCode(oriOrder.getProdCode());
        qpRefundOrder.setProdName(oriOrder.getProdName());
        qpRefundOrder.setCustomerCode(bo.getCustomerCode());
        qpRefundOrder.setCustomerName(oriOrder.getCustomerName());
        qpRefundOrder.setCustOrderNo(bo.getReOrderNo());
        qpRefundOrder.setCustOriOrderTime(oriOrder.getCustOrderTime());
        qpRefundOrder.setCustOriOrderNo(oriOrder.getCustOrderNo());
        qpRefundOrder.setInnerOriOrderNo(oriOrder.getInnerOrderNo());
        qpRefundOrder.setInnerOrderNo(refundOrderNo);
        qpRefundOrder.setCoreOriOrderNo(oriOrder.getCoreOrderNo());
        qpRefundOrder.setType(RefundBizTypeEnum.REFUND.getValue());
        qpRefundOrder.setAmount(bo.getBackReAmt());
        qpRefundOrder.setCcy(bo.getCurrency().getValue());
        qpRefundOrder.setReqTime(now);
        qpRefundOrder.setReqSourceType(OrderReqSourceTypeEnum.API.getValue());
        qpRefundOrder.setFee(reFundFeeAmt);
        qpRefundOrder.setFeeConfigType(oriOrder.getFeeConfigType());
        qpRefundOrder.setFeeConfigValue(oriOrder.getFeeConfigValue());
        qpRefundOrder.setDescription(bo.getRemark());
        qpRefundOrder.setStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
        qpRefundOrder.setReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());
        qpRefundOrder.setFeeReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());
        qpRefundOrder.setRemark(bo.getRemark());

        //增加原始訂單退款中金額，退款中手續費金額
        CreateRefundOrderBizBo bizBo = new CreateRefundOrderBizBo();
        bizBo.setQpRefundOrder(qpRefundOrder);
        ResultEx createRefundOrderBizResultEx = tradeBizService.createRefundOrder(bizBo);
        if (createRefundOrderBizResultEx.isFailed()) {
            return ResultEx.fail("退款下單失敗");//TODO 退款下單失敗 系統異常
        }

        //6 發起核心交易
        String sameCcy = qpRefundOrder.getCcy();
        CoreTradeDto coreTradeDto = new CoreTradeDto();
        coreTradeDto.setCcy(sameCcy);
        coreTradeDto.setDrCcy(sameCcy);
        coreTradeDto.setCrCcy(sameCcy);
        coreTradeDto.setAmt(qpRefundOrder.getAmount().toPlainString());
        //借方
        coreTradeDto.setDacTyp(AccountEnum.I);
        coreTradeDto.setDrAc(epQuickpayConfig.getSuspendAccNo());
        //貸方
        coreTradeDto.setCacTyp(AccountEnum.D);
        coreTradeDto.setCrAc(oriOrder.getBankAccNo());
        coreTradeDto.setCrNm(oriOrder.getBankAccName());
        //调用核心转账接口
        ResultEx<CoreTradeVo> coreTradeVoResultEx = coreTradeHandle.coreTrade(coreTradeDto, qpRefundOrder.getInnerOrderNo(), ePayOpenProperties.getCoreRefundRemark());

        RefundVo refundVo = new RefundVo();
        refundVo.setBankSerialNo(refundOrderNo);
        refundVo.setReOrderNo(bo.getReOrderNo());
        refundVo.setTransAmt(qpRefundOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
        refundVo.setReCurrency(sameCcy);

        CoreTradeVo coreTradeVo = coreTradeVoResultEx.getData();
        if (coreTradeVoResultEx.isFailed()
                || ObjectUtil.isEmpty(coreTradeVo)
                || ObjectUtil.isEmpty(coreTradeVo.getCMG26())) {
            log.info("調用核心失敗");
            //訂單結果未知  作調用超時處理 訂單狀態未知  定時任務，隊列同時處理
            //修改訂單狀態為超時
            RefundTimeoutBizBo refundTimeoutBizBo = new RefundTimeoutBizBo();
            refundTimeoutBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            refundTimeoutBizBo.setOldStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
            refundTimeoutBizBo.setNewStatus(QpRefundOrderStatusEnum.TIMEOUT.getValue());
            ResultEx bizTimeoutResultEx = tradeBizService.refundTimeout(refundTimeoutBizBo, coreTradeVoResultEx.getRetCode(), coreTradeVoResultEx.getRetMsg());
            if (bizTimeoutResultEx.isFailed()) {
                log.error("退款失敗並且無法保存數據庫");//TODO 如何处理
            }
            return ResultEx.makeResult(CoreRetCodeEnum.CORE_FAIL, refundVo);//TODO 核心交易失敗
        } else {
            LocalDateTime transDate = coreTradeVo.getTradeDate();
            String coreOrderNo = coreTradeVo.getCMG26().getJrnNo();
            qpRefundOrder.setCoreOrderNo(coreOrderNo);
            qpRefundOrder.setTransDate(transDate);
            //成功或失敗，修改原始订单退款中金額，已退款金额，退款中手續費金額，已退手續費金額， 修改退款訂單狀態
            RefundSuccessBizBo refundSuccessBizBo = new RefundSuccessBizBo();
            refundSuccessBizBo.setQpRefundOrder(qpRefundOrder);
            refundSuccessBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            refundSuccessBizBo.setCoreOrderNo(coreOrderNo);
            refundSuccessBizBo.setTransDate(transDate);
            refundSuccessBizBo.setOldStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
            refundSuccessBizBo.setNewStatus(QpRefundOrderStatusEnum.SUCCESS.getValue());
            ResultEx bizSuccessResultEx = tradeBizService.refundSuccess(refundSuccessBizBo);
            if (bizSuccessResultEx.isFailed()) {
                log.error("退款成功但是無法保存數據庫");
            }
            refundVo.setTfrDate(ObjectUtil.isNotEmpty(transDate) ? transDate.format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN)) : null);
            return ResultEx.ok(refundVo);
        }
    }

    /**
     * 快捷撤銷/沖正
     */
    public ResultEx<RefundVo> qpCancel(RefundBo bo) {
        LocalDateTime now = LocalDateTime.now();
        //查詢原始訂單
        QpOrder oriOrder = qpOrderService.getByCustOrderNo(bo.getCustomerCode(), bo.getOriOrderNo());
        //是否是當日交易
        if (oriOrder.getCreateTime().isBefore(LocalDate.now().atStartOfDay())) {
            //當日交易，走沖正
            return ResultEx.fail("僅支持撤銷當日訂單");//TODO 僅支持撤銷當日訂單
        }
        //1是否滿足沖正條件
        QpRefundOrder exist = qpRefundOrderService.getByInnerOriOrderNo(oriOrder.getInnerOrderNo());
        if (ObjectUtil.isNotEmpty(exist) && RefundBizTypeEnum.CANCEL.getValue().equals(exist.getType())) {
            return ResultEx.fail("不能重複撤銷");//TODO 不能重複撤銷
        }
        //交易金額必須一致
        if (oriOrder.getAmount().compareTo(bo.getBackReAmt()) != 0) {
            return ResultEx.fail("撤銷金額必須與原訂單金額相同！");//TODO 撤销金额必须与原订单支付金额相同
        }
        //是否有退款
        if (BigDecimal.ZERO.compareTo(oriOrder.getRefundAmt()) != 0 || BigDecimal.ZERO.compareTo(oriOrder.getRefundingAmt()) != 0) {
            return ResultEx.fail("原訂單有處理中的退款信息，無法撤銷！");//TODO 原訂單有處理中的退款信息，無法撤銷！
        }

        //1.2當前交易日訂單
        //獲取當前交易日起始時間
        BigDecimal customerRefundAmt = oriOrder.getAmount().subtract(oriOrder.getFee());

        //計算當前交易日 所有交易是否滿足退款條件  自然日
        //統計所有當前交易日所有交易總額及手續費總額
        //客戶當前交易餘額>customerRefundAmt,客戶當前交易日收取
        //查詢當日交易總額
        StatCustDayOrderAmtBo statCustDayOrder = new StatCustDayOrderAmtBo();
        statCustDayOrder.setCustomerCode(oriOrder.getCustomerCode());
        statCustDayOrder.setDate(LocalDate.now());
        statCustDayOrder.setStatusList(Lists.newArrayList(QpOrderStatusEnum.SUCCESS));
        ResultEx<CustDayOrderStatVo> custDayOrderStatVoResultEx = this.statCustDayOrderAmt(statCustDayOrder);
        CustDayOrderStatVo custDayOrderStatKo = custDayOrderStatVoResultEx.getData();
        //今日客户实际支出
        BigDecimal custDayRealPayAmt = custDayOrderStatKo.getOrderSumAmt()
                .subtract(custDayOrderStatKo.getRefundSumAmt())
                .subtract(custDayOrderStatKo.getRefundingSumAmt());
        //实际收取手续费
        BigDecimal custDayRealFeeAmt = custDayOrderStatKo.getFeeSumAmt()
                .subtract(custDayOrderStatKo.getRefundFeeSumAmt())
                .subtract(custDayOrderStatKo.getRefundingFeeSumAmt());

        //判斷今日客戶入賬 及 銀行手續費入賬金額是否滿足退款要求
        if (customerRefundAmt.compareTo(custDayRealPayAmt) > 0) {
            return ResultEx.fail();//TODO 當日客戶入賬少於撤銷金額
        }
        if (oriOrder.getFee().compareTo(custDayRealFeeAmt) > 0) {
            return ResultEx.fail();//TODO 當日客戶手續費入賬少於撤銷手續費金額
        }

        //生成沖正訂單，增加原訂單退款中金額及手續費金額
        String cancelOrderNo = NoGenUtil.genRefundOrderNo();
        QpRefundOrder qpRefundOrder = new QpRefundOrder();
        qpRefundOrder.setProdCode(oriOrder.getProdCode());
        qpRefundOrder.setProdName(oriOrder.getProdName());
        qpRefundOrder.setCustomerCode(oriOrder.getCustomerCode());
        qpRefundOrder.setCustomerName(oriOrder.getCustomerName());
        qpRefundOrder.setCustOrderNo(bo.getReOrderNo());
        qpRefundOrder.setCustOriOrderTime(oriOrder.getCustOrderTime());
        qpRefundOrder.setCustOriOrderNo(oriOrder.getCustOrderNo());
        qpRefundOrder.setInnerOriOrderNo(oriOrder.getInnerOrderNo());
        qpRefundOrder.setInnerOrderNo(cancelOrderNo);
        qpRefundOrder.setCoreOriOrderNo(oriOrder.getCoreOrderNo());
        qpRefundOrder.setAmount(oriOrder.getAmount());
        qpRefundOrder.setCcy(oriOrder.getCcy());
        qpRefundOrder.setReqTime(now);
        qpRefundOrder.setFee(oriOrder.getFee());
        qpRefundOrder.setFeeConfigType(oriOrder.getFeeConfigType());
        qpRefundOrder.setFeeConfigValue(oriOrder.getFeeConfigValue());
        qpRefundOrder.setDescription(oriOrder.getGoodsDesc());
        qpRefundOrder.setType(RefundBizTypeEnum.CANCEL.getValue());
        qpRefundOrder.setReqSourceType(OrderReqSourceTypeEnum.API.getValue());
        qpRefundOrder.setStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
        qpRefundOrder.setReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());
        qpRefundOrder.setFeeReconStatus(QpOrderReconStatusEnum.UN_RECON.getValue());
//        qpRefundOrder.setTransDate(0.getTransDate());
//        qpRefundOrder.setErrorCode();
//        qpRefundOrder.setErrorMessage();
//        qpRefundOrder.setNotifyUrl();
//        qpRefundOrder.setNotifyStatus();
//        qpRefundOrder.setCoreOrderNo(0.getCoreOrderNo());
        qpRefundOrder.setRemark("撤銷");

        CreateCancelOrderBizBo bizBo = new CreateCancelOrderBizBo();
        bizBo.setQpRefundOrder(qpRefundOrder);
        ResultEx cancelOrder = tradeBizService.createCancelOrder(bizBo);
        if (cancelOrder.isFailed()) {
            return ResultEx.fail("撤銷下單失敗"); //TODO 撤銷下單失敗
        }
        //發起核心沖正
        String sameCcy = qpRefundOrder.getCcy();
        CoreCancelDto coreCancelDto = new CoreCancelDto();
        coreCancelDto.setFlg(CoreCancelFlgEnum.INNER_ORDER_NO.getValue());
        coreCancelDto.setExJrn(oriOrder.getInnerOrderNo());
        coreCancelDto.setStmInd(CoreConfig.CANCEL_STMT_FLAG_N);
        coreCancelDto.setResn(ePayOpenProperties.getCoreRefundRemark());
        coreCancelDto.setExSys(ePayOpenProperties.getCoreReqSys());
        ResultEx<CoreCancelVo> coreResultEx = coreTradeHandle.coreCancel(coreCancelDto, qpRefundOrder.getInnerOrderNo());
        //調用核心接口結束
        RefundVo refundVo = new RefundVo();
        refundVo.setBankSerialNo(oriOrder.getSignNo());
        refundVo.setReOrderNo(cancelOrderNo);
        refundVo.setTransAmt(qpRefundOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
        refundVo.setReCurrency(sameCcy);
        refundVo.setBankSerialNo(oriOrder.getSignNo());
        //成功或失敗 修改原訂單狀態 修改原訂單退款金額及手續費金額，退款中金額及退款中手續費金額
        CoreCancelVo coreCancelVo = coreResultEx.getData();
        if (coreResultEx.isFailed()
                || ObjectUtil.isEmpty(coreCancelVo)) {
            RefundTimeoutBizBo refundTimeoutBizBo = new RefundTimeoutBizBo();
            refundTimeoutBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            refundTimeoutBizBo.setOldStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
            refundTimeoutBizBo.setNewStatus(QpRefundOrderStatusEnum.TIMEOUT.getValue());
            ResultEx bizTimeoutResultEx = tradeBizService.refundTimeout(refundTimeoutBizBo, coreResultEx.getRetCode(), coreResultEx.getRetMsg());
            if (bizTimeoutResultEx.isFailed()) {
                log.error("撤銷失敗並且無法保存數據庫");//TODO 如何处理
            }
            return ResultEx.makeResult(CoreRetCodeEnum.CORE_FAIL, refundVo);
        } else {
            LocalDateTime transDate = coreCancelVo.getTransDate();
            String coreOrderNo = coreCancelVo.getCoreJrnno();
            qpRefundOrder.setCoreOrderNo(coreOrderNo);
            qpRefundOrder.setTransDate(transDate);
            //成功或失敗，修改原始订单退款中金額，已退款金额，退款中手續費金額，已退手續費金額， 修改退款訂單狀態
            RefundSuccessBizBo refundSuccessBizBo = new RefundSuccessBizBo();
            refundSuccessBizBo.setQpRefundOrder(qpRefundOrder);
            refundSuccessBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            refundSuccessBizBo.setCoreOrderNo(coreOrderNo);
            refundSuccessBizBo.setTransDate(transDate);
            refundSuccessBizBo.setOldStatus(QpRefundOrderStatusEnum.PROCESSING.getValue());
            refundSuccessBizBo.setNewStatus(QpRefundOrderStatusEnum.SUCCESS.getValue());
            ResultEx bizSuccessResultEx = tradeBizService.refundSuccess(refundSuccessBizBo);
            if (bizSuccessResultEx.isFailed()) {
                log.error("撤銷成功但是無法保存數據庫");
            }
            refundVo.setTfrDate(ObjectUtil.isNotEmpty(transDate) ? transDate.format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN)) : null);
            return ResultEx.ok(refundVo);
        }
    }

    /**
     * 快捷訂單查詢
     * 時間段查詢、時間段+流水號、流水號
     * 對於支付交易查詢，返回的結果裡面只是所有成功的支付流水，失敗的不返回。
     */
    public ListResultEx<OrderVo> qpQuery(QueryOrderBo bo) {
        //当日  历史
        ResultEx validate = tradeValidator.verify(bo);
        if (validate.isFailed()) {
            return ListResultEx.fail("查詢條件有誤");
        }
        //支付 退款/撤销
        //订单号
        //位於不同的表
        //时间
        if (OrderQueryBizTypeEnum.PAY.getValue().equals(bo.getBizType())
                || OrderQueryBizTypeEnum.CHARGE.getValue().equals(bo.getBizType())) {
            //支付订单查询
            List<QpOrder> queryList = qpOrderService.queryPay(bo);
            List<OrderVo> result = BeanConvertUtil.convertList(queryList, (qpOrder) -> {
                OrderVo orderVo = new OrderVo();
                orderVo.setOrderNo(qpOrder.getCustOrderNo());
                orderVo.setBankSerialNo(qpOrder.getInnerOrderNo());
                orderVo.setOrderDate(DateTimeUtil.convert2Str(qpOrder.getCustOrderTime(), DatePattern.PURE_DATETIME_PATTERN));
                orderVo.setTfrDate(DateTimeUtil.convert2Str(qpOrder.getTransDate(), DatePattern.PURE_DATETIME_PATTERN));
                orderVo.setOrderAmt(qpOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
                orderVo.setAldyBackAmt(qpOrder.getRefundAmt().setScale(2, RoundingMode.HALF_UP).toPlainString());
                orderVo.setStatus(qpOrder.getStatus());
                orderVo.setTradeType(qpOrder.getTradeType());
                orderVo.setCurrency(qpOrder.getCcy());
                //orderVo.setPayRate("1.0");
                //orderVo.setReRate("1.0");
                return orderVo;
            });
            return ListResultEx.ok(result, result.size(), 1, result.size());
        } else if (OrderQueryBizTypeEnum.REFUND.getValue().equals(bo.getBizType())
                || OrderQueryBizTypeEnum.CANCEL.getValue().equals(bo.getBizType())) {
            //退款、撤销订单查询
            List<QpRefundOrder> queryList = qpRefundOrderService.queryRefundCancel(bo);
            List<OrderVo> result = BeanConvertUtil.convertList(queryList, (qpOrder) -> {
                OrderVo orderVo = new OrderVo();
                orderVo.setOrderNo(qpOrder.getCustOrderNo());
                orderVo.setOriOrderNo(qpOrder.getCustOriOrderNo());
                orderVo.setOriOrderDate(DateTimeUtil.convert2Str(qpOrder.getCustOriOrderTime(), DatePattern.PURE_DATETIME_PATTERN));
                orderVo.setBankSerialNo(qpOrder.getInnerOrderNo());
                orderVo.setTradeType(qpOrder.getType());
                orderVo.setOrderAmt(qpOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
                orderVo.setOrderDate(DateTimeUtil.convert2Str(qpOrder.getReqTime(), DatePattern.PURE_DATETIME_PATTERN));
                //1. if refundSuccess, get from order table
                if (OrderQueryBizTypeEnum.REFUND.getValue().equals(bo.getBizType())) {
                    QpOrder order = qpOrderService.getByCustOrderNo(bo.getCustomerCode(), orderVo.getOriOrderNo());
                    orderVo.setAldyBackAmt(order.getRefundAmt().setScale(2, RoundingMode.HALF_UP).toPlainString());
                } else {
                    //2.else use order amount
                    orderVo.setAldyBackAmt(qpOrder.getAmount().setScale(2, RoundingMode.HALF_UP).toPlainString());
                }
                orderVo.setStatus(qpOrder.getStatus());
                orderVo.setTfrDate(DateTimeUtil.convert2Str(qpOrder.getTransDate(), DatePattern.PURE_DATETIME_PATTERN));
                orderVo.setCurrency(qpOrder.getCcy());
                //orderVo.setPayRate("1.0");
                //orderVo.setReRate("1.0");
                return orderVo;
            });
            return ListResultEx.ok(result, result.size(), 1, result.size());
        } else {
            return ListResultEx.fail("未知類型");//TODO
        }
    }

    /**
     * 用戶訂單統計
     */
    public ResultEx<UserDayOrderStatVo> statUserDayOrderAmt(StatUserDayOrderAmtBo bo) {
        //查询当日
        LocalDateTime dayStart = bo.getDate().atStartOfDay();
        LocalDateTime dayEnd = bo.getDate().plusDays(1).atStartOfDay();

        StatUserDayOrderKo statUserDayOrderKo = new StatUserDayOrderKo();
        statUserDayOrderKo.setSignNo(bo.getSignNo());
        statUserDayOrderKo.setBankAccNo(bo.getBankAccNo());
        statUserDayOrderKo.setDayStart(dayStart);
        statUserDayOrderKo.setDayEnd(dayEnd);
        if (ObjectUtil.isNotEmpty(bo.getStatusList())) {
            List<String> statusList = bo.getStatusList().stream().map(QpOrderStatusEnum::getValue).collect(Collectors.toList());
            statUserDayOrderKo.setStatusList(statusList);
        }
        UserDayOrderStatKo userDayOrderStatKo = qpOrderService.countUserDayOrderInfo(statUserDayOrderKo);

        UserDayOrderStatVo vo = new UserDayOrderStatVo();
        vo.setOrderCount(userDayOrderStatKo.getOrderCount());
        vo.setOrderSumAmt(userDayOrderStatKo.getOrderSumAmt());
        vo.setFeeSumAmt(userDayOrderStatKo.getFeeSumAmt());
        vo.setRefundSumAmt(userDayOrderStatKo.getRefundSumAmt());
        vo.setRefundingSumAmt(userDayOrderStatKo.getRefundingSumAmt());
        vo.setRefundFeeSumAmt(userDayOrderStatKo.getRefundFeeSumAmt());
        vo.setRefundingFeeSumAmt(userDayOrderStatKo.getRefundingFeeSumAmt());
        return ResultEx.ok(vo);
    }

    /**
     * 客戶訂單統計
     */
    public ResultEx<CustDayOrderStatVo> statCustDayOrderAmt(StatCustDayOrderAmtBo bo) {
        //查询当日
        LocalDateTime dayStart = bo.getDate().atStartOfDay();
        LocalDateTime dayEnd = bo.getDate().plusDays(1).atStartOfDay();

        StatCustDayOrderKo statUserDayOrderKo = new StatCustDayOrderKo();
        statUserDayOrderKo.setCustomerCode(bo.getCustomerCode());
        statUserDayOrderKo.setDayStart(dayStart);
        statUserDayOrderKo.setDayEnd(dayEnd);
        if (ObjectUtil.isNotEmpty(bo.getStatusList())) {
            List<String> statusList = bo.getStatusList().stream().map(QpOrderStatusEnum::getValue).collect(Collectors.toList());
            statUserDayOrderKo.setStatusList(statusList);
        }
        CustDayOrderStatKo userDayOrderStatKo = qpOrderService.countCustDayOrderInfo(statUserDayOrderKo);

        CustDayOrderStatVo vo = new CustDayOrderStatVo();
        vo.setOrderCount(userDayOrderStatKo.getOrderCount());
        vo.setOrderSumAmt(userDayOrderStatKo.getOrderSumAmt());
        vo.setFeeSumAmt(userDayOrderStatKo.getFeeSumAmt());
        vo.setRefundSumAmt(userDayOrderStatKo.getRefundSumAmt());
        vo.setRefundingSumAmt(userDayOrderStatKo.getRefundingSumAmt());
        vo.setRefundFeeSumAmt(userDayOrderStatKo.getRefundFeeSumAmt());
        vo.setRefundingFeeSumAmt(userDayOrderStatKo.getRefundingFeeSumAmt());
        return ResultEx.ok(vo);
    }

    /**
     * 將超時的訂單號發佈到rabbitMQ
     *
     * @return
     */
    public ResultEx produceRetryQpPayOrders() {
        //TODO select all timeout orders and publish them to the queue.
        List<QpOrder> timeoutList = qpOrderService.getByStatus(QpOrderStatusEnum.TIMEOUT.getValue());
        timeoutList.forEach(qpOrder -> retryQpOrderProducer.send(qpOrder.getInnerOrderNo()));
        return ResultEx.ok("處理超時訂單數量: " + timeoutList.size());
    }

    /**
     * 將超時的退款訂單號發佈到rabbitMQ
     *
     * @return
     */
    public ResultEx produceRetryQpRefundOrders() {
        //TODO select all timeout orders and publish them to the queue.
        List<QpRefundOrder> timeoutList = qpRefundOrderService.getByStatus(QpRefundOrderStatusEnum.TIMEOUT.getValue());
        timeoutList.forEach(qpRefundOrder -> retryQpRefundOrderProducer.send(qpRefundOrder.getInnerOrderNo()));
        return ResultEx.ok("處理超時退款訂單數量: " + timeoutList.size());
    }

    /**
     * 執行重試超時的退款訂單
     *
     * @param innerOrderNo
     * @return
     */
    public ResultEx retryQpRefundOrder(String innerOrderNo) {
        QpRefundOrder qpRefundOrder = qpRefundOrderService.getByInnerOrderNo(innerOrderNo);
        log.info("重試超時退款訂單：{}", qpRefundOrder.getInnerOrderNo());
        //发起核心交易查证，查询订单状态
        GetCoreTranDto getCoreTranDto = new GetCoreTranDto();
        getCoreTranDto.setInqFlg(CoreInqFlgEnum.P.getValue());
        getCoreTranDto.setReqSys(ePayOpenProperties.getCoreReqSys());
        getCoreTranDto.setExRef(qpRefundOrder.getInnerOrderNo());
        ResultEx<CoreTranVo> coreTranResultEx = queryCoreHandle.getCoreTran(getCoreTranDto);
        CoreTranVo coreTranVo = coreTranResultEx.getData();
        if (coreTranResultEx.isFailed() || ObjectUtil.isEmpty(coreTranVo)) {
            log.error("退款查证失败。订单号:{}", qpRefundOrder.getInnerOrderNo());
        }
        if (CoreTransStatusEnum.N.getValue().equals(coreTranVo.getStatus())) {
            RefundSuccessBizBo successBizBo = new RefundSuccessBizBo();
            successBizBo.setQpRefundOrder(qpRefundOrder);
            successBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            successBizBo.setOldStatus(QpOrderStatusEnum.TIMEOUT.getValue());
            successBizBo.setNewStatus(QpOrderStatusEnum.SUCCESS.getValue());
            successBizBo.setCoreOrderNo(coreTranVo.getJrnNo());
            successBizBo.setTransDate(coreTranVo.getAcDt());
            ResultEx successBizResultEx = tradeBizService.refundSuccess(successBizBo);
            if (successBizResultEx.isFailed()) {
                log.error("订单状态修改失败, 系统订单号:{}", qpRefundOrder.getInnerOrderNo());
            }
        } else if (CoreTransStatusEnum.E.getValue().equals(coreTranVo.getStatus())) {
            RefundFailBizBo refundFailBizBo = new RefundFailBizBo();
            refundFailBizBo.setQpRefundOrder(qpRefundOrder);
            refundFailBizBo.setInnerOrderNo(qpRefundOrder.getInnerOrderNo());
            refundFailBizBo.setOldStatus(QpOrderStatusEnum.TIMEOUT.getValue());
            refundFailBizBo.setNewStatus(QpOrderStatusEnum.FAIL.getValue());
            refundFailBizBo.setCoreOrderNo(coreTranVo.getJrnNo());
            if (ObjectUtil.isNotEmpty(coreTranVo.getAcDt())) {
                refundFailBizBo.setTransDate(coreTranVo.getAcDt());
            }
            ResultEx failBizResultEx = tradeBizService.refundFail(refundFailBizBo);
            if (failBizResultEx.isFailed()) {
                log.error("订单状态修改失败，系统订单号:{}", qpRefundOrder.getInnerOrderNo());
            }
        } else {
            //其他 属异常情况 不做处理
            log.warn("查询核心订单状态异常，订单号:{},查询核心结果:{}", qpRefundOrder.getInnerOrderNo(), JSON.toJSONString(coreTranVo));
        }
        return ResultEx.ok();
    }

    /**
     * 執行重試超時的訂單
     */
    public ResultEx retryQpPayOrder(String innerOrderNo) {
        QpOrder qpOrder = qpOrderService.getByInnerOrderNo(innerOrderNo);
        log.info("重試超時訂單：{}", qpOrder.getInnerOrderNo());
        //发起核心交易查证，查询订单状态
        GetCoreTranDto getCoreTranDto = new GetCoreTranDto();
        getCoreTranDto.setInqFlg(CoreInqFlgEnum.P.getValue());
        getCoreTranDto.setReqSys(ePayOpenProperties.getCoreReqSys());
        getCoreTranDto.setExRef(qpOrder.getInnerOrderNo());
        ResultEx<CoreTranVo> coreTranResultEx = queryCoreHandle.getCoreTran(getCoreTranDto);
        CoreTranVo coreTranVo = coreTranResultEx.getData();
        if (coreTranResultEx.isFailed() || ObjectUtil.isEmpty(coreTranVo)) {
            log.error("交易查证失败。订单号:{}", qpOrder.getInnerOrderNo());
        }
        if (CoreTransStatusEnum.N.getValue().equals(coreTranVo.getStatus())) {
            TradeSuccessBizBo successBizBo = new TradeSuccessBizBo();
            successBizBo.setInnerOrderNo(qpOrder.getInnerOrderNo());
            successBizBo.setOldStatus(QpOrderStatusEnum.TIMEOUT.getValue());
            successBizBo.setNewStatus(QpOrderStatusEnum.SUCCESS.getValue());
            successBizBo.setCoreOrderNo(coreTranVo.getJrnNo());
            successBizBo.setTransDate(coreTranVo.getAcDt());
            ResultEx successBizResultEx = tradeBizService.tradeSuccess(successBizBo);
            if (successBizResultEx.isFailed()) {
                log.error("订单状态修改失败, 系统订单号:{}", qpOrder.getInnerOrderNo());
            }
        } else if (CoreTransStatusEnum.E.getValue().equals(coreTranVo.getStatus())) {
            TradeFailBizBo tradeFailBizBo = new TradeFailBizBo();
            tradeFailBizBo.setInnerOrderNo(qpOrder.getInnerOrderNo());
            tradeFailBizBo.setOldStatus(QpOrderStatusEnum.TIMEOUT.getValue());
            tradeFailBizBo.setNewStatus(QpOrderStatusEnum.FAIL.getValue());
            tradeFailBizBo.setCoreOrderNo(coreTranVo.getJrnNo());
            if (ObjectUtil.isNotEmpty(coreTranVo.getAcDt())) {
                tradeFailBizBo.setTransDate(coreTranVo.getAcDt());
            }
            ResultEx successBizResultEx = tradeBizService.tradeFail(tradeFailBizBo);
            if (successBizResultEx.isFailed()) {
                log.error("订单状态修改失败，系统订单号:{}", qpOrder.getInnerOrderNo());
            }
        } else {
            //其他 属异常情况 不做处理
            log.warn("查询核心订单状态异常，订单号:{},查询核心结果:{}", qpOrder.getInnerOrderNo(), JSON.toJSONString(coreTranVo));
        }

        return ResultEx.ok();
    }

}